#include "librarytree.h"
#include "librarytreeitem.h"

#include "componentdialog.h"
#include "librarydialog.h"
#include "librarymodel.h"
#include "qedalibrary.h"
#include "qedacomponent.h"
#include "../appsettings.h"

#include <QtCore/qdebug.h>
#include <QtGui/qcombobox.h>
#include <QtGui/qevent.h>
#include <QtGui/qfiledialog.h>
#include <QtGui/qmenu.h>
#include <QtGui/qmessagebox.h>

LibraryTree::LibraryTree(Mode mode, LibraryModel *libModel, QWidget *parent) :
    QTreeWidget(parent)
{
    m_mode = mode;
    m_libModel = libModel;
    setColumnCount(3);
    createHeaders();
    sortByColumn(0, Qt::AscendingOrder);
    setSortingEnabled(true);
    setSelectionMode(QAbstractItemView::ContiguousSelection);
    createActions();
    connect(m_libModel, SIGNAL(libraryAdded(QedaLibrary*)),
            this, SLOT(addLibrary(QedaLibrary*)));
    connect(m_libModel, SIGNAL(componentsAdded(QedaLibrary*,QList<QedaComponent*>)),
            this, SLOT(addComponents(QedaLibrary*,QList<QedaComponent*>)));
    connect(m_libModel, SIGNAL(componentUpdated(QedaComponent*)),
            this, SLOT(updateComponent(QedaComponent*)));
    connect(m_libModel, SIGNAL(componentRemoved(QedaComponent*)),
            this, SLOT(removeComponent(QedaComponent*)));
}

void LibraryTree::addComponents(QedaLibrary *lib, QList<QedaComponent*> components)
{
    setSortingEnabled(false);
    if (m_mode == LibraryMode)
    {
        LibraryTreeItem *libItem = lib->treeItem(m_mode);
        if (libItem)
        {
            QList<QTreeWidgetItem*> itemList;
            for (int i = 0; i < components.count(); ++i)
            {
                QedaComponent *component = components.at(i);
                LibraryTreeItem *newItem =
                        new LibraryTreeItem(libItem,
                                            QStringList() << component->displayName()
                                                          << component->reference()
                                                          << component->group(),
                                            LibraryTreeItem::ComponentItem);
                component->setTreeItem(m_mode, newItem);
                newItem->setObject(component);
                itemList.append(newItem);
            }
            libItem->addChildren(itemList);
            setCurrentItem(itemList.last());
        }
    }
    else if (m_mode == GroupMode)
    {
        QHash<QString,QedaComponentList> compTree;
        for (int i = 0; i < components.count(); ++i)
        {
            QString groupName = components.at(i)->group();
            compTree[groupName].append(components.at(i));
        }
        QStringList groups = compTree.keys();
        for (int i = 0; i < groups.count(); ++i)
        {
            QString groupName = groups.at(i);
            LibraryTreeItem *groupItem = findTopLevelItem(groupName);
            if (!groupItem)
                groupItem = newCategoryItem(groupName, LibraryTreeItem::GroupItem);

            const QedaComponentList groupComponents = compTree[groupName];
            QList<QTreeWidgetItem*> itemList;
            for (int j = 0; j < groupComponents.count(); ++j)
            {
                QedaComponent *component = groupComponents.at(j);
                LibraryTreeItem *newItem =
                        new LibraryTreeItem(groupItem,
                                        QStringList() << component->displayName()
                                                      << component->reference()
                                                      << component->library()->name(),
                                        LibraryTreeItem::ComponentItem);
                component->setTreeItem(m_mode, newItem);
                newItem->setObject(component);
                itemList.append(newItem);
            }
            groupItem->addChildren(itemList);
            setCurrentItem(itemList.last());
        }
    }
    else if (m_mode == ReferenceMode)
    {
        QHash<QString,QedaComponentList> compTree;
        for (int i = 0; i < components.count(); ++i)
        {
            QString refName = components.at(i)->reference();
            compTree[refName].append(components.at(i));
        }
        QStringList refs = compTree.keys();
        for (int i = 0; i < refs.count(); ++i)
        {
            QString refName = refs.at(i);
            LibraryTreeItem *refItem = findTopLevelItem(refName);
            if (!refItem)
                refItem = newCategoryItem(refName, LibraryTreeItem::ReferenceItem);

            const QedaComponentList refComponents = compTree[refName];
            QList<QTreeWidgetItem*> itemList;
            for (int j = 0; j < refComponents.count(); ++j)
            {
                QedaComponent *component = refComponents.at(j);
                LibraryTreeItem *newItem =
                        new LibraryTreeItem(refItem,
                                        QStringList() << component->displayName()
                                                      << component->group()
                                                      << component->library()->name(),
                                        LibraryTreeItem::ComponentItem);
                component->setTreeItem(m_mode, newItem);
                newItem->setObject(component);
                itemList.append(newItem);
            }
            refItem->addChildren(itemList);
            setCurrentItem(itemList.last());
        }
    }
    setSortingEnabled(true);
}

void LibraryTree::addLibrary(QedaLibrary *lib)
{
    if (m_mode == LibraryMode)
    {
        LibraryTreeItem *newItem = new LibraryTreeItem(this, QStringList() << lib->name(),
                                                       LibraryTreeItem::LibraryItem);
        emit categoryAdded(lib->name());
        lib->setTreeItem(m_mode, newItem);
        newItem->setObject(lib);
        newItem->setIcon(0, QIcon(":/images/library.png"));
        addTopLevelItem(newItem);
        if (!categoryFilter(newItem))
            setCurrentItem(newItem);
        //filter();
    }
}

bool LibraryTree::categoryFilter(QTreeWidgetItem *item)
{
    const bool hidden = m_filterCategory.isEmpty() ? false : item->text(0) != m_filterCategory;
    setItemHidden(item, hidden);
    return hidden;
}

void LibraryTree::componentCreate()
{
    LibraryTreeItem *item = static_cast<LibraryTreeItem*>(currentItem());
    ComponentDialog dlg(m_libModel, this);
    dlg.setWindowTitle(tr("Create Component"));
    if (item)
    {
        if (item->isLibrary())
            dlg.setLibrary(item->library());
        else if (item->isGroup())
            dlg.setGroup(item->text(0));
        else if (item->isReference())
            dlg.setReference(item->text(0));
    }
    if (dlg.exec() == QDialog::Accepted)
    {
        QedaComponent *component = dlg.library()->createComponent();
        dlg.dataTo(component);
        dlg.library()->addComponent(component);
    }
}

void LibraryTree::componentEdit()
{
    LibraryTreeItem *item = static_cast<LibraryTreeItem*>(currentItem());
    if (item && item->isComponent())
        emit componentEditRequested(item->component());
}

void LibraryTree::componentProperties()
{
    LibraryTreeItem *item = static_cast<LibraryTreeItem*>(currentItem());
    if (item && item->isComponent())
    {
        ComponentDialog dlg(m_libModel, this);
        dlg.setWindowTitle(tr("Component Properties"));
        QedaComponent *component = item->component();
        dlg.dataFrom(component);
        if (dlg.exec() == QDialog::Accepted)
            dlg.dataTo(component);
    }
}

void LibraryTree::componentRemove()
{
    LibraryTreeItem *item = static_cast<LibraryTreeItem*>(currentItem());
    if (item && item->isComponent())
    {
        int res = QMessageBox::question(this, tr("Remove Component"),
                                        tr("The component will be removed from library.<br><br>"
                                           "Are you sure?"),
                                        QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
        if (res == QMessageBox::Yes)
        {
            QedaComponent *component = item->component();
            component->library()->removeComponent(component);
            delete component;
        }
    }
}

void LibraryTree::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu contextMenu(this);
    LibraryTreeItem *item = static_cast<LibraryTreeItem*>(itemAt(event->pos()));
    if (!item)
    {
        contextMenu.addAction(m_actions["LibraryCreate"]);
        contextMenu.addAction(m_actions["LibraryLoad"]);
        contextMenu.addSeparator();
        if (m_libModel->libraries().count())
           contextMenu.addAction(m_actions["ComponentCreate"]);
    }
    else if (item->isLibrary() || item->isReference() || item->isGroup())
    {
        if (item->isLibrary())
        {
            contextMenu.addAction(m_actions["LibraryRemove"]);
            contextMenu.addSeparator();
        }
        contextMenu.addAction(m_actions["ComponentCreate"]);
    }
    else if (item->isComponent())
    {
        contextMenu.addAction(m_actions["ComponentEdit"]);
        contextMenu.addAction(m_actions["ComponentRemove"]);
    }
    contextMenu.exec(event->globalPos());
}

void LibraryTree::createActions()
{
    QAction *act;

    act = new QAction(QIcon(":/images/library_create.png"), tr("Create Library") + "...", this);
    connect(act, SIGNAL(triggered()), this, SLOT(libraryCreate()));
    m_actions["LibraryCreate"] = act;

    act = new QAction(QIcon(":/images/library_load.png"), tr("Load Library") + "...", this);
    connect(act, SIGNAL(triggered()), this, SLOT(libraryLoad()));
    m_actions["LibraryLoad"] = act;

    act = new QAction(QIcon(":/images/library_remove.png"), tr("Remove Library"), this);
    connect(act, SIGNAL(triggered()), this, SLOT(libraryRemove()));
    m_actions["LibraryRemove"] = act;

    act = new QAction(QIcon(":/images/component_create.png"), tr("Create Component") + "...", this);
    connect(act, SIGNAL(triggered()), this, SLOT(componentCreate()));
    m_actions["ComponentCreate"] = act;

    act = new QAction(QIcon(":/images/component_remove.png"), tr("Remove Component") + "...", this);
    connect(act, SIGNAL(triggered()), this, SLOT(componentRemove()));
    m_actions["ComponentRemove"] = act;

    act = new QAction(QIcon(":/images/component_edit.png"), tr("Edit Component"), this);
    QFont fontToBold = act->font();
    fontToBold.setBold(true);
    act->setFont(fontToBold);
    connect(act, SIGNAL(triggered()), this, SLOT(componentEdit()));
    m_actions["ComponentEdit"] = act;
}

void LibraryTree::createHeaders()
{
    switch (m_mode)
    {
    case LibraryMode:
        setHeaderLabels(QStringList() << tr(LABEL_LIBRARY) << tr(LABEL_REFERENCE) << tr(LABEL_GROUP));
        break;
    case GroupMode:
        setHeaderLabels(QStringList() << tr(LABEL_GROUP) << tr(LABEL_REFERENCE) << tr(LABEL_LIBRARY));
        break;
    case ReferenceMode:
        setHeaderLabels(QStringList() << tr(LABEL_REFERENCE) << tr(LABEL_GROUP) << tr(LABEL_LIBRARY));
        break;
    }
}

void LibraryTree::filterByCategory(int idx)
{
    QComboBox *filterBox = qobject_cast<QComboBox*>(sender());
    if (filterBox)
    {
        m_filterCategory = idx ? filterBox->itemText(idx) : "";
        for (int i = 0; i < topLevelItemCount(); ++i)
            categoryFilter(topLevelItem(i));
    }
}

LibraryTreeItem *LibraryTree::findTopLevelItem(const QString &name)
{
    LibraryTreeItem *item = 0;
    for (int i = 0; (i < topLevelItemCount()) && !item; ++i)
    {
        if (topLevelItem(i)->text(0) == name)
            item = static_cast<LibraryTreeItem*>(topLevelItem(i));
    }
    return item;
}

void LibraryTree::libraryCreate()
{
    LibraryDialog dlg(this);
    if (dlg.exec() == QDialog::Accepted)
    {
        g_appSettings->set("Library/Dir", dlg.directory());
        if (!m_libModel->containsLibrary(dlg.fullPath()))
            m_libModel->createLibrary(dlg.name(), dlg.fullPath());
    }
}

void LibraryTree::libraryLoad()
{
    QStringList fileTypes;
    fileTypes << tr("QEDA Library File (*.qlib)")
              << tr("All Files (*)");
    QString fileName = QFileDialog::getOpenFileName(this, tr("Load Library"),
                                                    g_appSettings->asString("Library/Dir"),
                                                    fileTypes.join(";;"));
    if (!fileName.isEmpty())
    {
        g_appSettings->set("Library/Dir", QFileInfo(fileName).absolutePath());
        m_libModel->loadLibrary(fileName);
    }
}

void LibraryTree::libraryRemove()
{
    LibraryTreeItem *item = static_cast<LibraryTreeItem*>(currentItem());
    if (item && item->isLibrary())
    {
        QedaLibrary *lib = item->library();
        if (lib->isModified())
        {
            int res = QMessageBox::question(this, tr("Save Library"),
                                            tr("The library has unsaved changes.<br><br>"
                                               "Would you save it?"),
                                            QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
            if (res == QMessageBox::Yes)
                lib->save();
        }
        emit categoryRemoved(lib->name());
        m_libModel->removeLibrary(lib);
    }
}

void LibraryTree::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
    {
        LibraryTreeItem *item = static_cast<LibraryTreeItem*>(currentItem());
        if (item && item->isComponent())
        {
            componentEdit();
            event->accept();
        }
    }
    QTreeWidget::mouseDoubleClickEvent(event);
}

LibraryTreeItem *LibraryTree::newCategoryItem(const QString &name, int itemType)
{
    LibraryTreeItem *newItem;
    newItem = new LibraryTreeItem(this, QStringList() << name,
                                  itemType);
    emit categoryAdded(name);
    newItem->setIcon(0, QIcon(":/images/library.png"));
    addTopLevelItem(newItem);
    newItem->setExpanded(true);
    categoryFilter(newItem);
    return newItem;
}

void LibraryTree::removeComponent(QedaComponent *component)
{
    LibraryTreeItem *item = component->treeItem(m_mode);
    if (item)
    {
        LibraryTreeItem *parentItem = static_cast<LibraryTreeItem*>(item->parent());
        if (parentItem)
            removeComponentItem(parentItem, item);
    }
}

void LibraryTree::removeComponentItem(LibraryTreeItem *categoryItem, LibraryTreeItem *item)
{
    categoryItem->removeChild(item);
    if (!categoryItem->childCount() && !categoryItem->isLibrary())
    {
        emit categoryRemoved(categoryItem->text(0));
        delete categoryItem;
    }
}

void LibraryTree::updateComponent(QedaComponent *component)
{
    LibraryTreeItem *item = component->treeItem(m_mode);
    if (item)
    {
        LibraryTreeItem *parentItem = static_cast<LibraryTreeItem*>(item->parent());
        if (m_mode == LibraryMode)
        {
            item->setText(0, component->displayName());
            item->setText(1, component->reference());
            item->setText(2, component->group());
        }
        else if (m_mode == GroupMode)
        {
            item->setText(0, component->displayName());
            item->setText(1, component->reference());
            item->setText(2, component->library()->name());
            if (parentItem && component->group() != parentItem->text(0))
            {
                removeComponentItem(parentItem, item);
                const QString groupName = component->group();
                LibraryTreeItem *groupItem = findTopLevelItem(groupName);
                if (!groupItem)
                    groupItem = newCategoryItem((groupName), LibraryTreeItem::GroupItem);
                groupItem->addChild(item);
                setCurrentItem(item);
            }
        }
        else if (m_mode == ReferenceMode)
        {
            item->setText(0, component->displayName());
            item->setText(1, component->group());
            item->setText(2, component->library()->name());
            if (parentItem && component->reference() != parentItem->text(0))
            {
                removeComponentItem(parentItem, item);
                const QString refName = component->reference();
                LibraryTreeItem *refItem = findTopLevelItem(refName);
                if (!refItem)
                    refItem = newCategoryItem(refName, LibraryTreeItem::ReferenceItem);
                refItem->addChild(item);
                setCurrentItem(item);
            }
        }
    }
}
